using util

class SourceGenerator : AbstractMain
{
  @Arg { help = "The directory where to generate the header and source files" }
  Uri? outputDir

  @Opt { help = "The pod whose header/source should be generated (e.g. sys)"; aliases = ["p"] }
  Str podName := "sys"

  @Opt { help = "The type whose header/source should be generated (e.g. sys::Str)"; aliases = ["t"] }
  Str? typeName


  static const Int kReturnTypeAlignment := 16


  override Int run()
  {
    types := (typeName != null) ? Type[Type.find(typeName)] : Pod.find(podName).types
    types.findAll { it.isPublic }.each |type|
    {
      generateHeader(type)
      generateSource(type)
    }
    return 0
  }


  Void generateHeader(Type type)
  {
    cStruct := typeCStruct(type)
    headerFile := File(outputDir.plusSlash + "${cStruct}.h".toUri)
    out := headerFile.out

    // Print the #ifndef/#define block
    headerDefine := typeHeaderDefine(type)
    out.printLine("#ifndef " + headerDefine)
    out.printLine("#define " + headerDefine)
    out.printLine

    // Print the includes
    if (type.base != null)
      out.printLine("#include \"" + typeCStruct(type.base) + ".h\"")
    else
    {
      out.printLine("#include \"Val.h\"")
      out.printLine("#include \"VTable.h\"")
    }
    out.printLine("#include \"SysTypes.h\"")
    out.printLine

    // Print the VTable macro
    vtableStruct := typeVTable(type)
    vtableMacro := typeVTableMacro(type)
    if (typeIsVal(type))
      out.printLine("#define $vtableMacro(self)   (($vtableStruct*)self.vtable)")
    else
      out.printLine("#define $vtableMacro(self)   (($vtableStruct*)((FanObj*)self)->vtable)")
    out.printLine
    out.printLine

    // Print the type/vtable extern declarations
    slotPrefix := "${type.name}_"
    out.printLine(returnTypeStr("extern Type*", 0, true) + "${slotPrefix}classType;")
    out.printLine(returnTypeStr("extern VTable*", 0, true) + "${slotPrefix}classVTable;")
    out.printLine
    out.printLine

    // Print the struct declaration
    out.printLine("/* [$type.qname] Attributes */")
    out.printLine("struct $cStruct")
    out.printLine("{")
    if (type.base == null)
      out.printLine(returnTypeStr("VTable*", 1, true) + "vtable;")
    else
      out.printLine(returnTypeStr(typeCStruct(type.base), 1, true) + "super;")
    out.printLine("};")
    out.printLine
    out.printLine

    // Print the vtable declaration
    virtualSlots := type.slots.findAll { it.isVirtual && !it.isOverride && it.parent == type && (it.isPublic || it.isProtected) }
    virtualSlots.sort |slot1, slot2| { slot1.name <=> slot2.name }
    out.printLine("/* [$type.qname] Virtual-table */")
    out.printLine("typedef struct $vtableStruct")
    out.printLine("{")
    if (type.base == null)
      out.printLine(returnTypeStr("VTable", 1, true) + "super;")
    else
      out.printLine(returnTypeStr(typeVTable(type.base), 1, true) + "super;")
    virtualSlots.each |slot|
    {
      prototypes := slotPrototypes(slot, "(*", ")", 1, true, false, false)
      prototypes.each |prototype|
      {
        out.printLine(prototype + ";")
      }
    }
    out.printLine("} $vtableStruct;")
    out.printLine
    out.printLine

    // Print the methods/fields declarations
    out.printLine("/* [$type.qname] Methods and fields */")
    slots := type.slots.findAll { it.isCtor }
    slots.addAll(type.slots.findAll { !it.isCtor })
    slots = slots.findAll { it.parent == type && (it.isPublic || it.isProtected) }
    slots.each |slot|
    {
      prototypes := slotPrototypes(slot, slotPrefix, "", 0, true, false, false)
      prototypes.each |prototype|
      {
        out.printLine("$prototype;")
      }
    }
    out.printLine
    out.printLine

    // Print the type-system function declarations
    out.printLine("/* [$type.qname] Type-system functions */")
    out.printLine(returnTypeStr("Type*", 0, true) + "${slotPrefix}initType(void);")
    out.printLine(returnTypeStr("VTable*", 0, true) + "${slotPrefix}initVTable(void);")
    out.printLine(returnTypeStr("void", 0, true) + "${slotPrefix}initStatic(void);")

    // Print the #endif
    out.printLine
    out.printLine
    out.printLine("#endif")

    out.close
  }



  Void generateSource(Type type)
  {
    cStruct := typeCStruct(type)
    sourceFile := File(outputDir.plusSlash + "${cStruct}.c".toUri)
    out := sourceFile.out

    // Print the #include block
    out.printLine("#include \"${cStruct}.h\"")
    out.printLine
    out.printLine("#include <string.h>")
    out.printLine("#include <fan-llvm/core/Core.h>")
    out.printLine("#include \"Type.h\"")
    out.printLine("#include \"UnsupportedErr.h\"")

    // Print the type/vtable declarations
    slotPrefix := "${type.name}_"
    out.printLine
    out.printLine
    out.printLine("Type* ${slotPrefix}classType = NULL;")
    out.printLine("VTable* ${slotPrefix}classVTable = NULL;")

    // Print the methods/fields implementations
    out.printLine
    out.printLine
    out.printLine("/**********************************************")
    out.printLine(" *")
    out.printLine(" * Methods and fields")
    out.printLine(" *")
    out.printLine(" **********************************************/")

    slots := type.slots.findAll { it.isCtor }
    slots.addAll(type.slots.findAll { !it.isCtor })
    slots = slots.findAll { it.parent == type && (it.isPublic || it.isProtected) }
    slots.each |slot|
    {
      prototypes := slotPrototypes(slot, slotPrefix, "", 0, false, false, false)
      prototypes.each |prototype|
      {
        out.printLine
        out.printLine
        out.printLine("/* " + slot.signature.replace("sys::", "") + " */")
        out.printLine(prototype)
        out.printLine("{")
        out.printLine("  exceptionThrow(UnsupportedErr_newWithFormattedMsg(\"$slot.qname not implemented yet\"));");
        out.printLine("}")
      }
    }

    // Print the method that builds the type
    out.printLine
    out.printLine
    out.printLine("/**********************************************")
    out.printLine(" *")
    out.printLine(" * Type-system functions")
    out.printLine(" *")
    out.printLine(" **********************************************/")

    typeVar := "${slotPrefix}classType"
    vtableStruct := typeVTable(type)
    out.printLine
    out.printLine
    out.printLine("Type* ${slotPrefix}initType(void)")
    out.printLine("{")
    out.printLine("  if ($typeVar == NULL)")
    out.printLine("  {")
    out.printLine("    Type* baseType = " + ((type.base != null) ? "${type.base.name}_initType()" : "NULL") + ";")
    out.printLine("    VTable* vtable = ${slotPrefix}initVTable();")
    out.printLine("    Type* type = Type_new(\"$type.name\", baseType, vtable, sizeof($cStruct), sizeof($vtableStruct));")
    slots = slots.sort |slot1, slot2|
    {
      slotType1 := slot1.isCtor ? 1 : (slot1.isMethod ? 2 : 3)
      slotType2 := slot2.isCtor ? 1 : (slot2.isMethod ? 2 : 3)
      if (slotType1 != slotType2)
        return slotType1 <=> slotType2
      else
        return slot1.name <=> slot2.name
    }
    slots.each |slot|
    {
      slotFunctions := slotPrototypes(slot, slotPrefix, "", 0, false, true, false)
      if (slot is Method)
      {
        method := (Method)slot
        methodType := method.isCtor ? "Ctor" : "Method"
        slotFunctions.each |slotFunction, i|
        {
          out.printLine("    Type_register${methodType}Implementation(type, \"$slot.name\", $slotFunction, " + (method.params.size - slotFunctions.size + i + 1) + ");")
        }
      }
      else
      {
        field := (Field)slot
        getter := slotFunctions.getSafe(0, "NULL")
        setter := slotFunctions.getSafe(1, "NULL")
        out.printLine("    Type_registerFieldImplementation(type, \"$slot.name\", $getter, $setter);")
      }
    }
    out.printLine("    $typeVar = type;")
    out.printLine("    ${slotPrefix}initStatic();")
    out.printLine("  }")
    out.printLine("  return $typeVar;")
    out.printLine("}")

    // Print the method that fills the vtable
    vtableVar := "${slotPrefix}classVTable"
    out.printLine
    out.printLine
    out.printLine("VTable* ${slotPrefix}initVTable(void)")
    out.printLine("{")
    out.printLine("  if ($vtableVar == NULL)")
    out.printLine("  {")
    out.printLine("    VTable* vtable = memoryAlloc(sizeof($vtableStruct));")
    if (type.base != null)
      out.printLine("    memcpy(vtable, ${type.base.name}_initVTable(), sizeof(" + typeVTable(type.base) + "));")
    virtualSlots := slots.findAll { it.isVirtual }
    virtualSlots.sort |slot1, slot2|
    {
      typeDepthComparaison := typeDepth(rootParentOfSlot(slot1)) <=> typeDepth(rootParentOfSlot(slot2))
      return (typeDepthComparaison != 0) ? typeDepthComparaison : slot1.name <=> slot2.name
    }
    virtualSlots.each |slot|
    {
      rootParent := rootParentOfSlot(slot)
      slotFunctions := slotPrototypes(slot, "", "", 0, false, true, false)
      slotFunctions.each |slotFunction|
      {
        out.printLine("    ((" + typeVTable(rootParent) + "*)vtable)->$slotFunction = ${slotPrefix}$slotFunction;")
      }
    }
    out.printLine("    $vtableVar = vtable;")
    out.printLine("  }")
    out.printLine("  return $vtableVar;")
    out.printLine("}")


    // Print the method that initializes the static variables
    out.printLine
    out.printLine
    out.printLine("void ${slotPrefix}initStatic(void)")
    out.printLine("{")
    out.printLine("}")

    out.close
  }


  Str[] slotPrototypes(Slot slot, Str prefix, Str suffix, Int indentationLevel, Bool alignReturnTypes, Bool nameOnly, Bool callOnly)
  {
    prototypes := Str[,]
    if (slot is Method)
    {
      method := (Method)slot
      method.params.each |param, i|
      {
        if (param.hasDefault)
          prototypes.add(methodPrototype(method, i, prefix, suffix, indentationLevel, alignReturnTypes, nameOnly, callOnly))
      }
      prototypes.add(methodPrototype(method, method.params.size, prefix, suffix, indentationLevel, alignReturnTypes, nameOnly, callOnly))
    }
    else if (slot is Field)
    {
      field := (Field)slot
      prototypes.add(getterPrototype(field, prefix, suffix, indentationLevel, alignReturnTypes, nameOnly, callOnly))
      if (!field.isConst)
        prototypes.add(setterPrototype(field, prefix, suffix, indentationLevel, alignReturnTypes, nameOnly, callOnly))
    }
    return prototypes
  }


  Str methodPrototype(Method method, Int numParams, Str prefix, Str suffix, Int indentationLevel, Bool alignReturnTypes, Bool nameOnly, Bool callOnly)
  {
    methodName := method.name
    if (numParams != method.params.size)
      methodName = methodName + "_" + numParams
    methodName = prefix + methodName + suffix
    if (nameOnly)
      return methodName

    returnType := ""
    params := Str[,]

    if (callOnly)
    {
      if (!method.isStatic)
        params.add("self")

      method.params[0..<numParams].each |param|
      {
        params.add(paramName(param))
      }
    }
    else
    {
      parentType := rootParentOfSlot(method)
      if (method.returns.name == "This")
        returnType = typeCType(parentType)
      else
        returnType = typeCType(method.returns)
      returnType = returnTypeStr(returnType, indentationLevel, alignReturnTypes)

      if (!method.isStatic)
        params.add(typeCType(parentType) + " self")

      method.params.eachRange(0..<numParams) |param|
      {
        params.add(typeCType(param.type) + " " + paramName(param))
      }

      if (params.isEmpty)
        params.add("void")
    }

    return returnType + methodName + "(" + params.join(", ") + ")"
  }


  Str getterPrototype(Field field, Str prefix, Str suffix, Int indentationLevel, Bool alignReturnTypes, Bool nameOnly, Bool callOnly)
  {
    getterName := prefix + field.name + suffix
    if (nameOnly)
      return getterName

    returnType := ""
    param := ""

    if (callOnly)
      param = field.isStatic ? "" : "self"
    else
    {
      parentType := rootParentOfSlot(field)
      returnType = typeCType(field.type)
      returnType = returnTypeStr(returnType, indentationLevel, alignReturnTypes)
      param = field.isStatic ? "void" : (typeCType(parentType) + " self")
    }

    return returnType + getterName + "(" + param + ")"
  }


  Str setterPrototype(Field field, Str prefix, Str suffix, Int indentationLevel, Bool alignReturnTypes, Bool nameOnly, Bool callOnly)
  {
    setterName := prefix + "set" + field.name.capitalize + suffix
    if (nameOnly)
      return setterName

    returnType := ""
    params := Str[,]

    if (callOnly)
    {
      if (!field.isStatic)
        params.add("self")
      params.add("val")
    }
    else
    {
      parentType := rootParentOfSlot(field)
      returnType = returnTypeStr("void", indentationLevel, alignReturnTypes)

      if (!field.isStatic)
        params.add(typeCType(parentType) + " self")
      params.add(typeCType(field.type) + " val")
    }
    return returnType + setterName + "(" + params.join(", ") + ")"
  }


  Str returnTypeStr(Str typeName, Int indentationLevel, Bool alignReturnTypes)
  {
    indentation := Str.spaces(2 * indentationLevel)
    returnTypeStr := indentation + typeName + " "
    return alignReturnTypes ? returnTypeStr.justl(kReturnTypeAlignment) : returnTypeStr
  }


  Str typeCType(Type type)
  {
    if (typeIsVal(type))
      return type.isNullable ? "Nullable${type.name}" : type.name
    if (type.name == "Void")
      return "void"
    if (type.name.size == 1)
    {
      if (type.name == "L")
        return "List*"
      if (type.name == "M")
        return "Map*"
      return "Val"
    }
    return "${type.name}*"
  }


  Str typeCStruct(Type type)
  {
    if (typeIsVal(type))
      return "Fan${type.name}"
    return type.name
  }


  Str typeVTable(Type type)
  {
    return typeCStruct(type) + "VTable"
  }


  Str paramName(Param param)
  {
    if (param.name == "char")
      return "ch"
    if (param.name == "register")
      return "reg"
    return param.name
  }


  Bool typeIsVal(Type type)
  {
    return (type.isVal || type.name == "Num" || type.name == "Obj");
  }


  Str typeVTableMacro(Type type)
  {
    vtableMacro := type.name.toDisplayName
    vtableMacro = vtableMacro.replace(" ", "_")
    vtableMacro = vtableMacro.upper + "_VTABLE"
    return vtableMacro
  }


  Str typeHeaderDefine(Type type)
  {
    structName := typeCStruct(type)
    headerDefine := structName.toDisplayName
    headerDefine = headerDefine.replace(" ", "_")
    headerDefine = headerDefine.upper
    headerDefine = "_" + headerDefine + "_H_"
    headerDefine = "_" + type.pod.name.upper + headerDefine
    return headerDefine
  }


  Type rootParentOfSlot(Slot slot)
  {
    rootParent := slot.parent
    if (!slot.isVirtual)
      return rootParent
    while (rootParent.base != null && rootParent.base.slot(slot.name, false) != null)
    {
      rootParent = rootParent.base
    }
    return rootParent
  }


  Int typeDepth(Type? type)
  {
    return (type != null) ? (typeDepth(type.base) + 1) : 0;
  }
}
